using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Threading;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.ComTypes;
using System.Diagnostics;

namespace Dazzle
{
    /// <summary>
    /// Presents our log messages to the user.
    /// </summary>
    internal partial class Log : Form, ILog
    {
        //public static Log form;
        private Mutex mutex;

        // For showing a tooltip over long items.
        private ToolTip toolTip = new ToolTip();
        private int lastIndex = -1;

        private static Log _logForm;

        public static ILog Instance {
            get {
                if (_logForm == null) {
                    _logForm = new Log();
                    _logForm.FormClosed += new FormClosedEventHandler(LogForm_FormClosed);
                }

                return _logForm;
            }
        }

        private static void LogForm_FormClosed(object sender, FormClosedEventArgs e) {
            Application.Exit();
        }

        private Log()
        {
            InitializeComponent();
            //toolTip. = true;
            //TopMost = true;
            // Where shall we place this?  We may have multiple instances
            // running and we do not want to obscure another
            // instance's log so we try to find an unused mutex, the number
            // of which will determine where on the screen we can go.
            int i = 0;
            bool got_it;
            mutex = new Mutex(true, "OLEBox log " + i.ToString(), out got_it);
            while (!got_it)
                mutex = new Mutex(true, "OLEBox log " + (++i).ToString(), out got_it);
            Rectangle screen = Screen.PrimaryScreen.WorkingArea;
            int x = screen.Right - Width;
            int y = 0;
            int offset = 0;
            while (i > 0) {
                if (y + Height * 2 > screen.Bottom) {
                    offset += 50;
                    x = screen.Right - Width - offset;
                    y = offset;
                } else {
                    y += Height;
                }
                i--;
            }
            SetBounds(x, y, Width, Height);
            Show();
            //string desktop = Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory);
            //logWriter = new StreamWriter(String.Format("{0}\\OLEBox.{1}.log", desktop, Process.GetCurrentProcess().Id));
            //logWriter.AutoFlush = true;
            //logWriter = null;  // stop attempts to write after close.
        }

        private void InstanceAdd(string s)
        {
            listMessages.Items.Add(s);
            listMessages.SelectedIndex = listMessages.Items.Count - 1;
        }

        // When everything else is finished this is called.  We do not exit the
        // application until the user acknowledges termination by closing this
        // log window.
        //public static void ReadyToExit()
        //{
        //    MethodInvoker f = delegate() {
        //        this.Text = "DONE - " + this.Text;
        //    };
        //    this.Invoke(f);
        //}

        private void Log_FormClosing(object sender, FormClosingEventArgs e)
        {
            _logForm = null;  // stop messages to avoid crashes
        }

        

        public static void LogClipboardContents()
        {
            Instance.Debug("Clipboard contents:");
            //using (new IndentLog("Clipboard contents:")) {
                System.Runtime.InteropServices.ComTypes.IDataObject data = (System.Runtime.InteropServices.ComTypes.IDataObject)Clipboard.GetDataObject();
                IEnumFORMATETC formats = data.EnumFormatEtc(DATADIR.DATADIR_GET);
                FORMATETC[] format = new FORMATETC[1];
                int[] fetched = new int[1];
                while (formats.Next(1, format, fetched) == 0) {
                    FORMATETC fmt = format[0];
                    _logForm.Debug(Util.FormatEtcToString(fmt));
                    if (fmt.cfFormat == Win32.RegisterClipboardFormat(Win32.CFSTR_OBJECTDESCRIPTOR)
                        || fmt.cfFormat == Win32.RegisterClipboardFormat(Win32.CFSTR_LINKSRCDESCRIPTOR)) {
                        STGMEDIUM medium;
                        data.GetData(ref fmt, out medium);
                        IntPtr buf = Win32.GlobalLock(medium.unionmember);
                        Win32.OBJECTDESCRIPTOR od = (Win32.OBJECTDESCRIPTOR)Marshal.PtrToStructure(buf, typeof(Win32.OBJECTDESCRIPTOR));
                        byte[] bytes = new byte[od.cbSize];
                        Marshal.Copy(buf, bytes, 0, od.cbSize);
                        _logForm.Debug(Encoding.Unicode.GetString(bytes, od.dwFullUserTypeName, od.dwSrcOfCopy - od.dwFullUserTypeName));
                        _logForm.Debug(Encoding.Unicode.GetString(bytes, od.dwSrcOfCopy, od.cbSize - od.dwSrcOfCopy));
                        Win32.GlobalUnlock(medium.unionmember);
                        Win32.GlobalFree(medium.unionmember);
                    }
                    if (fmt.cfFormat == Win32.CF_DIB && fmt.dwAspect == DVASPECT.DVASPECT_CONTENT
                        && (fmt.tymed & TYMED.TYMED_HGLOBAL) > 0) {
                        FORMATETC wanted = fmt;
                        wanted.tymed = TYMED.TYMED_HGLOBAL;
                        STGMEDIUM medium;
                        data.GetData(ref wanted, out medium);
                        IntPtr buf = Win32.GlobalLock(medium.unionmember);
                        System.Diagnostics.Debug.Assert(buf != IntPtr.Zero);
                        Win32.BITMAPFILEHEADER fh = (Win32.BITMAPFILEHEADER)Marshal.PtrToStructure(buf, typeof(Win32.BITMAPFILEHEADER));
                        Win32.BITMAPINFOHEADER ih = (Win32.BITMAPINFOHEADER)Marshal.PtrToStructure(buf, typeof(Win32.BITMAPINFOHEADER));
                        Win32.GlobalUnlock(medium.unionmember);
                        Win32.GlobalFree(medium.unionmember);
                    }
                    if (fmt.cfFormat == Win32.CF_ENHMETAFILE && fmt.dwAspect == DVASPECT.DVASPECT_CONTENT
                        && (fmt.tymed & TYMED.TYMED_ENHMF) > 0) {
                        FORMATETC wanted = fmt;
                        wanted.tymed = TYMED.TYMED_ENHMF;
                        STGMEDIUM medium;
                        data.GetData(ref wanted, out medium);
                        IntPtr hemf = medium.unionmember;
                        try {
                            Win32.ENHMETAHEADER emfh = Win32.EasyGetEnhMetaFileHeader(hemf);
                            _logForm.Debug(String.Format("rclBounds: {0}x{1}..{2}x{3}",
                                              emfh.rclBounds.Left, emfh.rclBounds.Right,
                                              emfh.rclBounds.Top, emfh.rclBounds.Bottom));
                            _logForm.Debug(String.Format("rclFrame: {0}x{1}..{2}x{3}",
                                              emfh.rclFrame.Left, emfh.rclFrame.Right,
                                              emfh.rclFrame.Top, emfh.rclFrame.Bottom));
                            _logForm.Debug(String.Format("szlDevice: {0}x{1}",
                                              emfh.szlDevice.cx, emfh.szlDevice.cy));
                            _logForm.Debug(String.Format("szlMillimeters: {0}x{1}",
                                              emfh.szlMillimeters.cx, emfh.szlMillimeters.cy));
                        } finally {
                            Win32.DeleteEnhMetaFile(hemf);
                        }
                    }
                }
            //}
        }

        
        delegate void OneString(string s);

        internal void Add(string type, string text)
        {
            int thread = Thread.CurrentThread.ManagedThreadId;
            //string indent = "".PadLeft(IndentLog.Current, ' ');
            string log_text = String.Format("[{0}]{1:x2} {2}{3}", type, thread, "", text);
            //if (logWriter != null)
            //    logWriter.WriteLine(s);
            //Console.WriteLine(log_text);
            //if (this != null) {
                OneString f = delegate(string s) {
                    //if (this != null)
                        this.InstanceAdd(s);
                };
                // We use asynchronous Invoke because otherwise we will deadlock
                // when the main thread uses Invoke() on another thread which
                // in turn calls this one.  Perhaps the logger should be in an
                // independent thread.
                this.BeginInvoke((Delegate)f, new object[] { log_text });
            //}
        }

        private void Add(string type, string text, Exception exception) {
            int thread = Thread.CurrentThread.ManagedThreadId;
            //string indent = "".PadLeft(IndentLog.Current, ' ');
            string log_text = String.Format("[{0}]{1:x2} {2}{3}:{4}", type, thread, "", text, exception.Message);

            //if (form != null) {
                OneString f = delegate(string s) {
                    //if (form != null)
                        this.InstanceAdd(s);
                };
                // We use asynchronous Invoke because otherwise we will deadlock
                // when the main thread uses Invoke() on another thread which
                // in turn calls this one.  Perhaps the logger should be in an
                // independent thread.
                this.BeginInvoke((Delegate)f, new object[] { log_text });

                string[] splitExceptions = exception.ToString().Split('\n');
                foreach (string str in splitExceptions) {
                    this.BeginInvoke((Delegate)f, new object[] { str });
                }
           // }
        }

        internal void SetTitle(string title)
        {
            this.Text = title;
        }

        private void listMessages_MouseMove(object sender, MouseEventArgs e)
        {
            int index = listMessages.IndexFromPoint(e.X, e.Y);
            // Ensure that there is an item and that the mouse is over a new
            // index.
            string tip = "";
            bool show = false;
            if (index != ListBox.NoMatches) {
                tip = listMessages.Items[index].ToString();

                // Get the text extent.
                Win32.SIZE size;
                IntPtr hdc = Win32.GetDC(listMessages.Handle);
                try {
                    size.cx = 0;
                    size.cy = 0;
                    Win32.GetTextExtentPoint32(hdc, tip, tip.Length, ref size);
                } finally {
                    Win32.ReleaseDC(listMessages.Handle, hdc);
                }

                // If it won't fit show tool-tip.
                show = Width < size.cx;
            }
            if (show) {
                if (lastIndex != index) {
                    toolTip.SetToolTip(listMessages, tip);
                }
                toolTip.Active = true;
            } else {
                toolTip.SetToolTip(listMessages, "");
                toolTip.Active = false;
            }
            lastIndex = index;
        }

        #region ILog Members

        #region Info

        public void Info(string message) {
            Add("INFO", message);
        }

        public void Info(string format, object arg) {
            Add("INFO", string.Format(format, arg));
        }

        public void Info(string format, object arg0, object arg1) {
            Add("INFO", string.Format(format, arg0, arg1));
        }

        public void Info(string format, object arg0, object arg1, object arg2) {
            Add("INFO", string.Format(format, arg0, arg1,arg2));
        }

        public void Info(string format, params object[] args) {
            Add("INFO", string.Format(format, args));
        }

        public void Info(string message, Exception exception) {
            Add("INFO", message, exception);
        }

        #endregion

        #region Error

        public void Error(string message) {
            Add("ERROR", message);
        }

        public void Error(string format, object arg) {
            Add("ERROR", string.Format(format, arg));
        }

        public void Error(string format, object arg0, object arg1) {
            Add("ERROR", string.Format(format, arg0, arg1));
        }

        public void Error(string format, object arg0, object arg1, object arg2) {
            Add("ERROR", string.Format(format, arg0, arg1, arg2));
        }

        public void Error(string format, params object[] args) {
            Add("ERROR", string.Format(format, args));
        }

        public void Error(string message, Exception exception) {
            Add("ERROR", message, exception);
        }

        #endregion

        #region Debug

        public void Debug(string message) {
            Add("DEBUG", message);
        }

        public void Debug(string format, object arg) {
            Add("DEBUG", string.Format(format, arg));
        }

        public void Debug(string format, object arg0, object arg1) {
            Add("DEBUG", string.Format(format, arg0, arg1));
        }

        public void Debug(string format, object arg0, object arg1, object arg2) {
            Add("DEBUG", string.Format(format, arg0, arg1, arg2));
        }

        public void Debug(string format, params object[] args) {
            Add("DEBUG", string.Format(format, args));
        }

        public void Debug(string message, Exception exception) {
            Add("DEBUG", message, exception);
        }

        #endregion

        #region Warn

        public void Warn(string message) {
            Add("WARN", message);
        }

        public void Warn(string format, object arg) {
            Add("WARN", string.Format(format, arg));
        }

        public void Warn(string format, object arg0, object arg1) {
            Add("WARN", string.Format(format, arg0, arg1));
        }

        public void Warn(string format, object arg0, object arg1, object arg2) {
            Add("WARN", string.Format(format, arg0, arg1, arg2));
        }

        public void Warn(string format, params object[] args) {
            Add("WARN", string.Format(format, args));
        }

        public void Warn(string message, Exception exception) {
            Add("WARN", message, exception);
        }

        #endregion

        #endregion
    }

    /// <summary>
    /// When you have a lot of logging, it can be hard to work out what is
    /// calling what.  We get around this by indenting log messages under
    /// messages added by a LogIndent, until the LogIndent is destroyed.
    /// </summary>
    //public class IndentLog : IDisposable
    //{
    //    [ThreadStatic]
    //    static int logIndent = 0;

    //    const int INDENT_STEP = 3;

    //    public IndentLog(string s)
    //    {
    //        Log.Add(s);
    //        logIndent += INDENT_STEP;
    //    }

    //    public IndentLog(string s, object o1)
    //    {
    //        Log.Add(s, o1);
    //        logIndent += INDENT_STEP;
    //    }

    //    public IndentLog(string s, object o1, object o2)
    //    {
    //        Log.Add(s, o1, o2);
    //        logIndent += INDENT_STEP;
    //    }

    //    public IndentLog(string s, object o1, object o2, object o3)
    //    {
    //        Log.Add(s, o1, o2, o3);
    //        logIndent += INDENT_STEP;
    //    }

    //    public void Dispose()
    //    {
    //        Log.Add("done");
    //        logIndent -= INDENT_STEP;
    //    }

    //    public static int Current { get { return logIndent; } }
    //}
}